package com.turbo.cloud.core.log.alarm;

import cn.hutool.json.JSONUtil;
import jakarta.servlet.ServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

@Aspect
@Slf4j
@Component
@Order(2)
public class ErrorAlarmAspect {

    @Pointcut("@annotation(com.turbo.cloud.core.log.alarm.ErrorAlarm)")
    public void errorAlarm() {
        // 方法是空的，因为这只是一个切入点，实现在advice中。
    }

    /**
     * 环绕通知
     * 1、@Around 的 value 属性：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     * 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法：继续下一个通知或目标方法调用，返回处理结果，如果目标方法发生异常，则 proceed 会抛异常.
     * 3、假如目标方法是控制层接口，则本方法的异常捕获与否都不会影响目标方法的事务回滚
     * 4、假如目标方法是控制层接口，本方法 try-catch 了异常后没有继续往外抛，则全局异常处理 @RestControllerAdvice 中不会再触发
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("errorAlarm()")
    public Object around(ProceedingJoinPoint joinPoint) {
        if (log.isDebugEnabled()) {
            log.debug("【环绕通知-开始】methodName={}", joinPoint.toLongString());
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        //region 代码块-获取注解参数
        final ErrorAlarm errorAlarm = this.getMethodAnnotation(signature, ErrorAlarm.class);

        String msg = errorAlarm.message();
        String serverName = errorAlarm.serverName();

        if (log.isDebugEnabled()) {
            log.debug("【环绕通知-执行】msg::" + msg + ";" + "serverName::" + serverName);
        }
        //endregion

        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();

        //region 代码块-获取Http的请求方式和请求地址
        //获取Http的请求方式和请求地址
        HttpMethodAttribute httpMethodAttribute = getHttpMethodAttribute(signature);
        String[] methodNameParameters = signature.getParameterNames();

        if (Objects.nonNull(httpMethodAttribute)) {
            if (log.isDebugEnabled()) {
                log.debug("【环绕通知-执行】注解在接口上::httpMethodType::" + httpMethodAttribute.getHttpMethod() + ";httpMethodPath::" + httpMethodAttribute.getHttpPath() + ";");
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("【环绕通知-执行】注解在方法上");
            }
        }
        //endregion

        //region 代码块-获取方法的参数
        //获取方法的参数
        StringBuilder argStr = new StringBuilder();
        try {
            String parameterStr;
            for (Object o : joinPoint.getArgs()) {
                if (o instanceof ServletRequest) {
                    ServletRequest req = (ServletRequest) o;
                    Map<String, String[]> parameterMap = req.getParameterMap();
                    StringBuilder sb = new StringBuilder(64).append("?");
                    parameterMap.forEach((k, v) -> {
                        sb.append(k).append("=");
                        if (v != null && v.length > 0 && v[0] != null) {
                            sb.append(v[0]).append("&");
                        }
                    });
                    parameterStr = sb.toString();
                    argStr.append(parameterStr);
                } else {
                    argStr.append(o.getClass().getSimpleName()).append("=").append(JSONUtil.toJsonStr(o));
                }
            }
        } catch (Exception e) {
            log.error("【环绕通知-执行】解析参数异常::", e);
            argStr = new StringBuilder(Arrays.toString(joinPoint.getArgs()));
        }
        //endregion

        if (log.isDebugEnabled()) {
            log.debug("【环绕通知-执行】methodName::" + className + ";" + "methodName::" + methodName + ";");
            log.debug("【环绕通知-执行】parameterKeys::{}", JSONUtil.toJsonStr(methodNameParameters));
            log.debug("【环绕通知-执行】parameters::{}", argStr);
        }

        //方法的返回值
        Object result = null;
        try {
            result = joinPoint.proceed();

            if (log.isDebugEnabled()) {
                log.debug("【环绕通知-执行】result::{}", result);
            }
        } catch (Throwable e) {
            log.error("【环绕通知-执行】error::", e);

        }

        if (log.isDebugEnabled()) {
            log.debug("【环绕通知-结束】methodName={}", joinPoint.toLongString());
        }

        return result;
    }

    /**
     * 前置通知：目标方法执行之前执行以下方法体的内容。
     * value：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     *
     * @param joinPoint：提供对连接点处可用状态和有关它的静态信息的反射访问
     */
    @Before(value = "errorAlarm()")
    public void aspectBefore(JoinPoint joinPoint) {
        if (log.isDebugEnabled()) {
            log.debug("【前置通知】methodName={}", joinPoint.toLongString());
        }
    }

    /**
     * 返回通知：目标方法返回后执行以下代码
     * value 属性：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     * pointcut 属性：绑定通知的切入点表达式，优先级高于 value，默认为 ""
     * returning 属性：通知签名中要将返回值绑定到的参数的名称，默认为 ""
     *
     * @param joinPoint ：提供对连接点处可用状态和有关它的静态信息的反射访问
     * @param result    ：目标方法返回的值，参数名称与 returning 属性值一致。无返回值时，这里 result 会为 null.
     */
    @AfterReturning(pointcut = "errorAlarm()", returning = "result")
    public void aspectAfterReturning(JoinPoint joinPoint, Object result) {
        if (log.isDebugEnabled()) {
            log.debug("【返回通知】,shortString={},result={}", joinPoint.toShortString(), result);
        }
        //if(result == error) {
        //    //给用户预警
        //}
    }


    /**
     * 后置通知：目标方法执行之后执行以下方法体的内容，不管目标方法是否发生异常。
     * value：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     *
     * @param joinPoint：提供对连接点处可用状态和有关它的静态信息的反射访问
     */
    @After(value = "errorAlarm()")
    public void aspectAfter(JoinPoint joinPoint) {
        if (log.isDebugEnabled()) {
            log.debug("【后置通知】methodName={}", joinPoint.toLongString());
        }
    }

    /**
     * 异常通知：目标方法发生异常的时候执行以下代码，此时返回通知不会再触发
     * value 属性：绑定通知的切入点表达式。可以关联切入点声明，也可以直接设置切入点表达式
     * pointcut 属性：绑定通知的切入点表达式，优先级高于 value，默认为 ""
     * throwing 属性：与方法中的异常参数名称一致，
     *
     * @param e：捕获的异常对象，名称与 throwing 属性值一致
     */
    @AfterThrowing(pointcut = "errorAlarm()", throwing = "e")
    public void aspectAfterThrowing(JoinPoint joinPoint, Exception e) {
        if (log.isDebugEnabled()) {
            log.debug("【异常通知】methodName={}", joinPoint.toLongString());
        }
        String methodName = joinPoint.getSignature().getName();
        if (e instanceof ArithmeticException) {
            log.error("【异常通知->异常报错】" + methodName + "方法算术异常（ArithmeticException）：", e);
        } else {
            log.error("【异常通知->异常报错】" + methodName + "方法异常：", e);
        }
    }

    /**
     * 获取注解类
     *
     * @param signature 方法签名
     * @return HttpMethodAttribute http请求属性
     */
    private HttpMethodAttribute getHttpMethodAttribute(MethodSignature signature) {
        HttpMethodAttribute httpMethodAttribute = null;
        PostMapping postMapping = signature.getMethod().getAnnotation(PostMapping.class);
        if (postMapping != null) {
            httpMethodAttribute = new HttpMethodAttribute();

            //请求方式
            httpMethodAttribute.setHttpMethod(HttpMethod.POST.name());
            //请求地址
            httpMethodAttribute.setHttpPath(postMapping.value()[0]);

            return httpMethodAttribute;
        }
        GetMapping getMapping = signature.getMethod().getAnnotation(GetMapping.class);
        if (getMapping != null) {
            httpMethodAttribute = new HttpMethodAttribute();

            //请求方式
            httpMethodAttribute.setHttpMethod(HttpMethod.GET.name());
            //请求地址
            httpMethodAttribute.setHttpPath(getMapping.value()[0]);

            return httpMethodAttribute;
        }
        PutMapping putMapping = signature.getMethod().getAnnotation(PutMapping.class);
        if (putMapping != null) {
            httpMethodAttribute = new HttpMethodAttribute();

            //请求方式
            httpMethodAttribute.setHttpMethod(HttpMethod.PUT.name());
            //请求地址
            httpMethodAttribute.setHttpPath(putMapping.value()[0]);

            return httpMethodAttribute;
        }
        DeleteMapping deleteMapping = signature.getMethod().getAnnotation(DeleteMapping.class);
        if (deleteMapping != null) {
            httpMethodAttribute = new HttpMethodAttribute();

            //请求方式
            httpMethodAttribute.setHttpMethod(HttpMethod.DELETE.name());
            //请求地址
            httpMethodAttribute.setHttpPath(deleteMapping.value()[0]);

            return httpMethodAttribute;
        }
        RequestMapping requestMapping = signature.getMethod().getAnnotation(RequestMapping.class);
        if (requestMapping != null) {
            httpMethodAttribute = new HttpMethodAttribute();

            //请求方式
            httpMethodAttribute.setHttpMethod(requestMapping.method()[0].name());
            //请求地址
            httpMethodAttribute.setHttpPath(deleteMapping.value()[0]);

            return httpMethodAttribute;
        }
        return httpMethodAttribute;
    }

    /**
     * 获取注解类
     *
     * @param signature       方法签名
     * @param annotationClass 注解类
     * @return Annotation 注解类
     */
    private <T extends Annotation> T getMethodAnnotation(MethodSignature signature, Class<T> annotationClass) {
        return signature.getMethod().getAnnotation(annotationClass);
    }
}
